package com.ae.shield.common.recipe.assembly;

import com.google.common.base.Preconditions;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.ae.shield.common.fluid.FluidHelper;
import com.ae.shield.common.recipe.ModRecipesType;
import com.ae.shield.common.tag.TagHelper;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.fluid.Fluid;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.*;
import net.minecraft.network.PacketBuffer;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.ITag;
import net.minecraft.util.JSONUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.RecipeMatcher;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.registries.ForgeRegistryEntry;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.IntStream;

public class AssemblyRecipe implements IAssemblyRecipe {
    protected final ResourceLocation id;
    protected final ItemStack result;
    protected final Fluid fluid;
    protected final ITag<Fluid> fluidTag;
    protected final int fluidCount;
    protected final int energy;
    protected final int tick;
    protected final NonNullList<Ingredient> itemInputs;
    protected final boolean isSimple;

    public AssemblyRecipe(ResourceLocation id, ItemStack result, Fluid fluid, ITag<Fluid> fluidTag, int fluidCount, int energy, int tick, Ingredient... itemInputs) {
        Preconditions.checkArgument(itemInputs.length <= 13, "最多有13个原料");
        this.id = id;
        this.result = result;
        this.fluid = fluid;
        this.fluidTag = fluidTag;
        this.fluidCount = fluidCount;
        this.energy = energy;
        this.tick = tick;
        this.itemInputs = NonNullList.from(Ingredient.EMPTY, itemInputs);
        this.isSimple = this.itemInputs.stream().allMatch(Ingredient::isSimple);
    }

    @Override
    public boolean matches(FluidStack fluid, NonNullList<ItemStack> stacks) {
        if (fluid.getAmount() < this.fluidCount || (fluid.getFluid() != this.fluid && (fluidTag != null && !fluidTag.contains(fluid.getFluid())))
                || stacks.size() != itemInputs.size() || !itemInputs.get(0).test(stacks.get(0))) {
            return false;
        }

        RecipeItemHelper helper = new RecipeItemHelper();
        ArrayList<ItemStack> inputs = new ArrayList<>();
        int i = 0;

        for (int j = 0; j < stacks.size(); j++) {
            ItemStack stack = stacks.get(i);
            if (!stack.isEmpty()) {
                ++i;
                if (isSimple) {
                    helper.func_221264_a(stack, 1);
                } else inputs.add(stack);
            }
        }

        return i == itemInputs.size() && (isSimple ? helper.canCraft(this, null) : RecipeMatcher.findMatches(inputs, itemInputs) != null);
    }

    @Override
    public int getEnergy() {
        return energy;
    }

    @Override
    public int getTick() {
        return tick;
    }

    @Override
    public Fluid getFluid() {
        return fluid;
    }

    @Override
    public ITag<Fluid> getFluidTag() {
        return fluidTag;
    }

    @Override
    public int getFluidCount() {
        return fluidCount;
    }

    @Override
    public boolean matches(@Nonnull IInventory inv, @Nonnull World worldIn) {
        return false;
    }

    @Nonnull
    @Override
    public ItemStack getCraftingResult(@Nonnull IInventory inv) {
        return result.copy();
    }

    @Nonnull
    @Override
    public ItemStack getRecipeOutput() {
        return result;
    }

    @Nonnull
    @Override
    public ResourceLocation getId() {
        return id;
    }

    @Nonnull
    @Override
    public NonNullList<Ingredient> getIngredients() {
        return itemInputs;
    }

    @Nonnull
    @Override
    public IRecipeSerializer<?> getSerializer() {
        return ModRecipesType.ASSEMBLY_SERIALIZER.get();
    }

    public static class AssemblyRecipeType implements IRecipeType<AssemblyRecipe> {
        @Override
        public String toString() {
            return "assembly";
        }
    }

    public static class Serializer extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<AssemblyRecipe> {

        @Nonnull
        @Override
        public AssemblyRecipe read(@Nonnull ResourceLocation id, @Nonnull JsonObject json) {
            ItemStack result = ShapedRecipe.deserializeItem(JSONUtils.getJsonObject(json, "result"));

            Fluid fluid = null;
            ITag<Fluid> fluidTag = null;
            int fluidCount = 0;

            if (JSONUtils.hasField(json, "fluid_ingredient")) {
                JsonObject fluidJson = JSONUtils.getJsonObject(json, "fluid_ingredient");
                fluid = FluidHelper.deserializeFluid(fluidJson);

                if (fluid == null) {
                    fluidTag = TagHelper.deserializeFluidTag(fluidJson);
                }

                fluidCount = JSONUtils.getInt(fluidJson, "count");
            }

            int energy = JSONUtils.getInt(json, "energy_per_tick");
            int tick = JSONUtils.getInt(json, "processing_time");

            JsonArray ingredients = JSONUtils.getJsonArray(json, "ingredients");
            Ingredient base = Ingredient.deserialize(JSONUtils.getJsonArray(json, "backing"));
            List<Ingredient> inputs = new ArrayList<>();
            inputs.add(base);

            for (JsonElement e : ingredients) {
                inputs.add(Ingredient.deserialize(e));
            }

            return new AssemblyRecipe(id, result, fluid, fluidTag, fluidCount, energy, tick, inputs.toArray(new Ingredient[0]));
        }

        @Nullable
        @Override
        public AssemblyRecipe read(@Nonnull ResourceLocation id, @Nonnull PacketBuffer buffer) {
            ItemStack result = buffer.readItemStack();

            int fluidFlag = buffer.readInt();
            Fluid fluid = null;
            ITag<Fluid> fluidTag = null;

            switch (fluidFlag) {
                case 1:
                    FluidStack fluidStack = buffer.readFluidStack();
                    fluid = fluidStack.getFluid();
                    break;
                case 2:
                    fluidTag = FluidTags.createOptional(buffer.readResourceLocation());
                    break;
            }

            int[] ints = buffer.readVarIntArray(3);
            Ingredient[] inputs = new Ingredient[buffer.readVarInt()];
            for (int i = 0; i < inputs.length; i++) {
                inputs[i] = Ingredient.read(buffer);
            }
            return new AssemblyRecipe(id, result, fluid, fluidTag, ints[0], ints[1], ints[2], inputs);
        }

        @Override
        public void write(PacketBuffer buffer, AssemblyRecipe recipe) {
            buffer.writeItemStack(recipe.result);

            if (recipe.fluid != null) {
                buffer.writeInt(1);
                buffer.writeFluidStack(new FluidStack(recipe.fluid, 1));
            } else if (recipe.fluidTag != null) {
                buffer.writeInt(2);
                buffer.writeResourceLocation(((ITag.INamedTag<Fluid>) recipe.fluidTag).getName());
            } else {
                buffer.writeInt(0);
            }

            buffer.writeVarIntArray(new int[]{recipe.fluidCount, recipe.energy, recipe.tick});
            buffer.writeVarInt(recipe.itemInputs.size());
            for (Ingredient input : recipe.getIngredients()) {
                input.write(buffer);
            }
        }
    }
}
